<?php
/**
 * 路径和controller对应类
 * 
 * 利用RewriteRule实现了route和controller关系的映射
 * 
 * 典型的URL为：<br/>
 *  http://iframework/index/add?i=1&j=2<br/>
 * 此时将会调用'index' controller的'add' action，并传入参数i和j
 * 
 * @version $Id: IRoute.php 323 2008-05-14 05:49:20Z iwind $
 * @package util.core
 */
class IRoute extends IObject {
	/**
	 * URI封装
	 *
	 * @var IURI
	 */
	private $uri;
	
	private $mvcConfig;
	
	/**
	 * Enter description here...
	 *
	 * @var IApplication
	 */
	private $controller;
	
	private $action;
	
	private $dispatcher = "/index.php";
	
	private $excludes = array();
	
	/**
	 * 构造函数
	 *
	 * @param IURI $uri 当前请求的URI
	 */
	public function __construct(IURI $uri) {
		$this->uri = $uri;
		$this->mvcConfig = IConfig::getMvcConfig();
		$this->excludes[] = $this->dispatcher;
		$this->parse();
	}
	
	
	/**
	 * Enter description here...
	 *
	 * @since 0.1
	 */
	private function parse() {
		$path = $this->uri->getPath();
		ILogger::debug("enter path '{$path}'");
		
		//从0.2.1起，不再提示
		if ($path == $this->dispatcher && $this->uri->getQuery() == "") {
//			throw new IException("please enter your controller and action to visit required resource.such as: <br/>\n&nbsp;&nbsp;/Route.php/mycontroller/myaction<br/>\n&nbsp;&nbsp;/Route.php?controller=mycontroller&action=myaction<br/>\n&nbsp;&nbsp;/mycontroller/myaction");
		}
		
		//filter mapping
		$filterMapping = IConfig::getFilterMapping();
		$pathFilters = array();
		foreach ($filterMapping as $filter) {
			if (isset($filter["path"]) && isset($filter["filters"])) {
				$isExluded = false;
				if (isset($filter["excludes"]) && is_array($filter["excludes"])) {
					foreach($filter["excludes"] as $excludePattern) {
						if (preg_match($excludePattern, $path)) {
							$isExluded = true;
						}
					}
				}
				if (!$isExluded) {
					if (is_array($filter["filters"])) {
						foreach($filter["filters"] as $filterName) {
							$filterObject = IConfig::getFilter($filterName);
						}
					}
					$pathFilters[] = $filterObject;
				}
			}
		}
		
		//route arguments
		$params = array();
		
		//get from query
		$controller = IApplication::getControllerFromQuery();
		$action = IApplication::getActionFromQuery();
		
		//get controller and action from routing
		if (is_null($controller)) {
			if (!in_array($path, $this->excludes) && is_array($this->mvcConfig)) {
				if (isset($this->mvcConfig["route"]["patterns"])) {
					$patterns = $this->mvcConfig["route"]["patterns"];
					if (is_array($patterns)) {
						foreach ($patterns as $pattern) {
							$match = null;
							if (preg_match($pattern["path"], $path, $match)) {
								foreach ($pattern as $argKey=>$argValue) {								
									if (native::isValidVariableName($argKey)) {
										$$argKey = @preg_replace("|\\$(\\d+)|e", "\$match[\\1]", $argValue);
										$params[$argKey] = $$argKey;
									}
								}					
								break;//找到即跳出
							}
						}
					}
				}
			}
		}
		
		/**
		 * 处理组件
		 * 
		 * @since 0.2.1
		 */ 
		if (isset($component)) {
			$this->loadComponent($component);
			return;
		}
		
		/**
		 * 从路径/a/b/c/d 中分析到 a.b.c/d 
		 * 
		 * @since 0.2.1
		 */
		if ($controller === null) {
			$trace = debug_backtrace();
			$lastTrace = array_pop($trace);
			$routeFile = basename($lastTrace["file"]);//入口文件
			if (preg_match("/^(.*" . preg_quote($routeFile, "/") . ")(.*)$/U", $path, $match)) {
				$controllerAction = IApplication::getControllerFromPath($match[1], $match[2]);
			}
			else if(function_exists("apache_get_modules") && !in_array("mod_rewrite", apache_get_modules())) {//如果不支持重写
				$controllerAction = IApplication::getControllerFromPath(rtrim($path, "/") . "/" . $routeFile, "/");
			}
			else if (!is_file(".htaccess")) {
				$controllerAction = IApplication::getControllerFromPath(rtrim($path, "/") . "/" . $routeFile, "/");
			}
			else {//rewrite重写
				$dir = str_replace("\\", "/", dirname($lastTrace["file"]));
				preg_match("/(.+)\|\\1/", $dir . "|" . $path,  $match);
				$base = empty($match)?"" : $match[1];
				$path = preg_replace("/^" . preg_quote($base, "/") . "/", "", $path);
				$controllerAction = IApplication::getControllerFromPath($base, $path);
			}
			if (!empty($controllerAction)) {
				$controller = $controllerAction["controller"];
				$action = $controllerAction["action"];
			}
		}
		
		//find controller infomation
		if ($controller === null) {
			if (!defined("IF_COMPONENT_NAME")) {
				throw new IException("can not find route for '{$this->uri}'");	
			}
			else {
				throw new IException("can not find route for '{$this->uri}' in component '" . IF_COMPONENT_NAME . "'");
			}
		}
		if (!isset($this->mvcConfig["controllers"][$controller])) {
			//尝试自己查找类
			$controller = trim($controller, ".");
			$className = $this->getControllerByName($controller);
			if ($className === null) {
				throw new IException("can not find controller named '" . $controller . "'");
			}
			else {
				$controllerInfo["class"] = $className;
			}
		}
		else {
			$controllerInfo = IConfig::getControllerConfig($controller);
		}
		$className = $controllerInfo["class"];
		$this->controller = IObjectFactory::getSingleObject($className);
		if ($this->controller == null) {
			throw new IException("can't load controller class '{$className}'");
		}
		if (!($this->controller instanceof IApplication)) {
			throw new IException("controller class '{$className}' must be inherited from IApplication");
		}
		
		//set name
		$this->controller->setController($controller);
		$this->controller->setAction($action);
		$this->action = $action;
		
		//path filters
		$this->controller->addFilters($pathFilters);
		
		//controller filters
		if (isset($controllerInfo["filters"]) && is_array($controllerInfo["filters"])) {
			foreach ($controllerInfo["filters"] as $filterName) {
				$this->controller->addFilter(IConfig::getFilter($filterName));
			}
		}
		
		//add params
		foreach ($params as $key=>$value) {
			$this->controller->addParam($key, $value);
		}
	}
	
	/**
	 * 执行当前URI对应的controller
	 *
	 */
	public function run() {
		if (!defined("__ROUTE__")) {
			define("__ROUTE__", "");
		}
		$this->controller->run();
	}
	
	/**
	 * 取得当前请求的controller
	 *
	 * @return IApplication
	 */
	public function getController() {
		return $this->controller;
	}
	
	/**
	 * 取得当前请求的action
	 *
	 * @return string
	 */
	public function getAction() {
		return $this->action;
	}
	
	private function getControllerByName($name) {
		$pieces = explode(".", $name);
		$useful = array();
		if (empty($pieces)) {
			return null;
		}
		$controller = array_pop($pieces);
		$class = ucfirst($controller) . "Controller";
		foreach ($pieces as $piece) {
			if (!preg_match("/^[\\w\\-]+$/", $piece)) {
				return null;
			}
			$useful[] = $piece;
		}
		if (empty($useful)) {
			return $class;
		}
		else {
			return implode(".", $useful) . "." . $class;
		}
 	}
 	
 	/**
 	 * 加载组件
 	 * 
 	 * @param string $component 组件名
 	 * @since 0.2.1
 	 */
 	private function loadComponent($component) {
 		if (defined("IF_COMPONENT_NAME")) {
 			throw new IException("can not define another component in current component");
 		}
 		
 		//有无注册
 		$registry = IConfig::getKeysValue($this->mvcConfig, "component.registry.{$component}");
 		$package = $component;
 		$dir = $component;
 		$path = "/" . $component;
 		if (is_array($registry)) {
 			if (isset($registry["dir"])) {
 				$dir = $registry["dir"];
 			}
 			if (isset($registry["path"])) {
 				$path = $registry["path"];
 			}
 		}
 		else {
 			throw new IException("the component is not registered, should be in [component.registry.{$component}]");
 		}
 		
 		//设置常量
 		define("IF_COMPONENT_NAME", $component);
 		define("IF_COMPONENT_PATH", $path);
 		
 		//查找
 		$base = IConfig::getKeysValue($this->mvcConfig, "component.base");
 		if ($base === null) {
 			$base = IFRAMEWORK_APP_ROOT . "/app/components";
 		}
 		$comDir = $base . "/" . $dir;
 		if (!is_dir($comDir)) {
 			throw new IException("can not find component '{$component}' at '{$comDir}'");
 		}
 		
 		//加载组件配置
 		$comConfigFile = $comDir . "/config/component.conf.php";
 		if (!is_file($comConfigFile)) {
 			throw new IException("component config file '{$comConfigFile}' of component '{$component}' is not found");
 		}
 		$comConfig = require($comConfigFile);
 		if (!is_array($comConfig))  {
 			throw new IException("component config of '{$component}' is invalid, please check '{$comConfigFile}' again");
 		}
 		if (isset($comConfig["package"])) {
 			$package = $comConfig["package"];
 		}
 		define("IF_COMPONENT_PACKAGE", $package);
 		$package = str_replace(".", "/", $package);
 		
 		//mvc配置
 		$mvcConfigFile = $comDir . "/config/mvc.conf.php";
 		if (!is_file($mvcConfigFile)) {
 			throw new IException("mvc config file '{$mvcConfigFile}' of component '{$component}' is not found");
 		}
 		
 		//根目录
 		define("IF_COMPONENT_ROOT", $comDir);
 		
 		//加载组件config
 		$this->mvcConfig = IConfig::loadMvc($mvcConfigFile);
 		
 		
 		//设置class-path
 		$appConfig = &IConfig::getAppConfig();
 		$appConfig["class-path"] = array();//清空
 		IConfig::addClassPath($comDir . "/controllers/" . $package);
 		IConfig::addClassPath($comDir . "/helpers/" . $package);
 		IConfig::addClassPath($comDir . "/models/" . $package);
 		
 		//重新parse
 		$this->parse();
 	}
}

?>